(ns pinyin.core
  (:require [clojure.string :as str]
            #?@(:cljs [[pinyin.dict :refer [dict]]]
                :clj [[clojure.java.io :as io]
                      [clojure.edn :as edn]])))

;;java函数有64k的大小限制，因此不能像cljs那样直接定义dict的字面量
#?(:clj (def dict (-> "dict.edn"
                      io/resource
                      slurp
                      edn/read-string)))

(defn- char-code
  "字符编码"
  [ch]
  #?(:clj (int ch)
     ;;注意，js没有字符类型，只有字符串
     :cljs (.charCodeAt ch 0)))

(defn- char-py
  "汉字字符的拼音"
  [ch]
  (get dict (char-code ch)))

(defn py
  "将字符串中的汉字转换为拼音首字母，忽略非汉字
  返回一个序列，序列的元素是由对应汉字的多种读音的首字母组成的字符串
  当reserve-letter?参数为true时保留非汉字字符并转为小写，否则丢弃非汉字字符"
  ([string] (py string false))
  ([string reserve-letter?]
   (let [tr (cond-> char-py reserve-letter? (some-fn str))]
     (keep tr string))))

(defn pinyin
  "将字符串中的汉字转换为拼音首字母，忽略非汉字
  与py函数不同，本函数返回一个字符串，多音字的多个首字母用小括号包围
  reserve-letter?参数的含义与上相同"
  ([string] (pinyin string false))
  ([string reserve-letter?]
   (->> (py string reserve-letter?)
        (map #(if (> (count %) 1) (str "(" % ")") %))
        (apply str))))

(defn- match?
  "判断两个拼音是否匹配
  pinyin是完整版(字符串序列)，py是简化版(字符序列)
  options是一个映射，包含以下键:
  :successive? 表示必须连续匹配
  :fixed-at-start? 表示是否固定在起始位置匹配
  :southern? 表示是否按南方音模糊匹配
  所谓南方音就是不区分n和l以及h和f"
  [pinyin py options]
  (if (empty? py)
    true
    (if (empty? pinyin)
      false
      (let [s1 (first pinyin) l1 (first py)]
        ;;有两种情况匹配成功
        ;;1.第一个字符匹配，并且后续的pinyin也匹配后续的py
        ;;2.在不要求头部匹配的情况下，后续的pinyin匹配整个py
        (or (and (or (str/index-of s1 l1)
                     (when (:southern? options)
                       (case l1
                         \n (str/index-of s1 \l)
                         \l (str/index-of s1 \n)
                         \h (str/index-of s1 \f)
                         \f (str/index-of s1 \h)
                         false)))
                 (match? (rest pinyin) (rest py)
                         (assoc options :fixed-at-start? (:successive? options))))
            (and (not (:fixed-at-start? options))
                 (match? (rest pinyin) py options)))))))

(defn py?
  "判断汉字字符串string是否与拼音首字母字符串py相容"
  ([string pinyin] (py? string pinyin nil))
  ([string pinyin options]
   (match? (py string (:successive? options))
           (seq pinyin)
           (assoc options :fixed-at-start? false))))

(defn- py-seq
  "切割字符串，考虑小括号
  ```Clojure
  (py-seq \"a(bc)(de)f\")
  ;;=> (\"a\" \"bc\" \"de\" \"f\")
  ```
  "
  [pinyin]
  (lazy-seq
   (when-not (str/blank? pinyin)
     (if (str/starts-with? pinyin "(")
       (let [i (str/index-of pinyin ")")]
         (cons (subs pinyin 1 i)
               (py-seq (subs pinyin (inc i)))))
       (cons (subs pinyin 0 1)
             (py-seq (subs pinyin 1)))))))

(defn pinyin?
  "比较两个字符串是否能部分匹配
  pinyin是某汉字字符串经函数pinyin变换得到的字符串
  py是简化版拼音"
  ([pinyin py] (pinyin? pinyin py nil))
  ([pinyin py options]
   (match? (py-seq pinyin)
           (seq py)
           (assoc options :fixed-at-start? false))))
